// InfraTreeFactory 关系图谱物理拓扑方法聚合，为上层domain提供基础调用
package aggregate

import (
	"fmt"
	"gluenet/internal/glue-srvp/domain/dependency"
	"gluenet/internal/glue-srvp/domain/entity"
	"gluenet/internal/pkg/infra"
	"gluenet/internal/pkg/infra/prepare"
	"gluenet/pkg/log"
	"path/filepath"
	"time"
)

func init() {
	prepare.RegisterFactory(func() {
		factory := NewInfraTreeFactory()
		prepare.InjectDependency(func() *InfraTreeFactory {
			return factory
		})
	})
}

type InfraTreeFactory struct {
	MetaRepo dependency.MetaRepo
}

func NewInfraTreeFactory() *InfraTreeFactory {
	factory := &InfraTreeFactory{}
	prepare.FetchDependency(&factory.MetaRepo)
	return factory
}

func (r *InfraTreeFactory) GetTree(organize string) (interface{}, error) {
	var tree entity.InfraTreeNode
	if err := r.MetaRepo.FindKV(filepath.ToSlash(filepath.Join(infra.InfraTreeRepoKey, organize)), &tree); err != nil {
		return nil, err
	}
	return tree, nil
}

func (r *InfraTreeFactory) CleanTree(organize string) error {
	if err := r.MetaRepo.DelWithPrefix(filepath.ToSlash(filepath.Join(infra.InfraTreeRepoKey, organize))); err != nil {
		return err
	}
	if err := r.MetaRepo.PutKV(filepath.ToSlash(filepath.Join(infra.InfraTreeRepoKey, organize)), map[string]interface{}{}); err != nil {
		return err
	}
	return nil
}

func (r *InfraTreeFactory) BuildTree(organize string) error {
	var maas []entity.Agent
	if err := r.MetaRepo.FindBy(&maas, map[string]interface{}{
		"platform": infra.PlatformMaas,
	}); err != nil {
		return err
	}

	var k8s []entity.Agent
	if err := r.MetaRepo.FindBy(&k8s, map[string]interface{}{
		"platform": infra.PlatformKubernetes,
	}); err != nil {
		return err
	}

	var areaAll []entity.Area
	if err := r.MetaRepo.FindAll(&areaAll); err != nil {
		return err
	}

	var instance []entity.Instance
	if err := r.MetaRepo.FindAll(&instance); err != nil {
		return err
	}

	var area = filterArea(areaAll, maas, k8s)

	var tree = &entity.InfraTreeNode{}
	var areaMapToNode = make(map[string]*entity.InfraTreeNode)
	if err := makeArea(area, tree, &areaMapToNode); err != nil {
		return err
	}

	var guidMapToNode = make(map[string]*entity.InfraTreeNode)
	if err := makeMaas(&areaMapToNode, &guidMapToNode, maas); err != nil {
		return err
	}

	var clusterMapToNode = make(map[string]*entity.InfraTreeNode)
	if err := makeCluster(&clusterMapToNode, &guidMapToNode, &areaMapToNode, k8s); err != nil {
		return err
	}

	// todo make instance
	if err := makeInstance(&clusterMapToNode, instance); err != nil {
		return err
	}

	r.MetaRepo.PutKV(filepath.ToSlash(filepath.Join(infra.InfraTreeRepoKey, organize)), tree)
	return nil
}

func countAreaSize(tree *entity.InfraTreeNode) error {
	tree.ChildSize = len(tree.ChildData)
	return nil
}

func countAreaSizeD(tree *entity.InfraTreeNode) {

}

func filterArea(obj []entity.Area, maas, k8s []entity.Agent) []entity.Area {
	var area []entity.Area
	for _, v := range obj {
		match := false
		for _, n := range maas {
			if n.AreaID == v.AreaID {
				match = true
				break
			}
		}
		for _, n := range k8s {
			if n.AreaID == v.AreaID {
				match = true
				break
			}
		}
		if match {
			area = append(area, v)
		}
	}
	return area
}

func makeInstance(areaC *map[string]*entity.InfraTreeNode, objs []entity.Instance) error {
	now := time.Now()
	//fmt.Printf("make instace %v", objs)
	for _, v := range objs {
		cluster, ok := (*areaC)[v.RunTimeRepoValue.ClusterAgentGuid]
		if !ok {
			log.Errorf("application can't find machine on tree, guid: %v, agent: %v", v.GUID, v.RunTimeRepoValue.ClusterAgentGuid)
			continue
		}
		for _, node := range cluster.ChildData {
			nodeName := node.Name
			root := &entity.InfraTreeNode{}
			root.Create = now
			root.Update = now
			root.Type = infra.TreeInstance
			root.State = "alive"
			root.Info = v.DetailRepoValue
			root.Name = v.Name
			root.Guid = v.GUID
			for podName, podNodeName := range v.RunTimeRepoValue.PodsOnNodes {
				if podNodeName == nodeName {
					pod := &entity.InfraTreeNode{}
					pod.Create = now
					pod.Update = now
					pod.Type = infra.TreePods
					pod.State = "alive"
					pod.Info = map[string]string{
						"appName": v.Name,
					}
					pod.Name = podName
					pod.Guid = v.GUID

					for containerName, cpodName := range v.RunTimeRepoValue.ContainerInPods {
						if podName == cpodName {
							container := &entity.InfraTreeNode{}
							container.Create = now
							container.Update = now
							container.Type = infra.TreeContainer
							container.State = "alive"
							container.Info = map[string]string{
								"appName": v.Name,
								"podName": cpodName,
							}
							container.Name = containerName
							container.Guid = v.GUID

							pod.ChildData = append(pod.ChildData, container)
							//root.ChildData = append(root.ChildData, container)
						}
					}

					pod.ChildSize = len(pod.ChildData)
					root.ChildData = append(root.ChildData, pod)
				}
			}

			root.ChildSize = len(root.ChildData)
			node.ChildData = append(node.ChildData, root)
		}
	}
	return nil
}

func makeCluster(areaC, areaG, area *map[string]*entity.InfraTreeNode, k8s []entity.Agent) error {
	now := time.Now()
	for _, v := range k8s {
		if v.DetailRepoValue.ParentGuid != "" {
			if parent, ok := (*areaG)[v.DetailRepoValue.ParentGuid]; ok {
				var machinesIncluster []*entity.InfraTreeNode
				var machinesNotIncluster []*entity.InfraTreeNode
				for _, n := range parent.ChildData {
					notIn := true
					for _, n2 := range v.RunTimeRepoValue.ClusterNodes {
						if n.Name == n2.NodeName {
							notIn = false
							n.Info = map[string]interface{}{
								"maas_guid":    n.Guid,
								"cluster_guid": v.GUID,
							}
							n.Guid = v.GUID
							machinesIncluster = append(machinesIncluster, n)
						}
					}
					if notIn {
						machinesNotIncluster = append(machinesNotIncluster, n)
					}
				}

				var cluster = &entity.InfraTreeNode{}
				cluster.Create = now
				cluster.Update = now
				// todo next version to be cluster
				cluster.Type = infra.TreeCluster
				//cluster.Type = infra.TreeZone
				cluster.State = "alive"
				cluster.Info = v.DetailRepoValue
				cluster.Name = v.DetailRepoValue.ClusterName
				cluster.Guid = v.GUID
				cluster.ChildData = machinesIncluster
				cluster.ChildSize = len(machinesIncluster)
				machinesNotIncluster = append(machinesNotIncluster, cluster)
				(*areaG)[v.DetailRepoValue.ParentGuid].ChildData = machinesNotIncluster
				(*areaC)[v.GUID] = cluster
			} else {
				return fmt.Errorf("k8s agent parent guid don't exsit")
			}
		} else {
			root := &entity.InfraTreeNode{}
			root.Create = now
			root.Update = now
			//root.Type = infra.TreeZone
			root.Type = infra.TreeCluster
			root.State = "alive"
			root.Info = v.DetailRepoValue
			root.Name = v.DetailRepoValue.ClusterName
			root.Guid = v.GUID

			for _, n := range v.RunTimeRepoValue.ClusterNodes {
				child := &entity.InfraTreeNode{}
				child.Create = now
				child.Update = now
				child.Type = infra.TreeMachine
				child.State = "alive"
				child.Info = n
				child.Name = n.NodeName
				child.Guid = v.GUID

				root.ChildData = append(root.ChildData, child)
			}

			(*area)[v.AreaID].ChildData = append((*area)[v.AreaID].ChildData, root)
			(*areaC)[v.GUID] = root
		}
	}

	return nil
}

func makeMaas(area *map[string]*entity.InfraTreeNode, areaG *map[string]*entity.InfraTreeNode, maas []entity.Agent) error {
	now := time.Now()
	for _, v := range maas {
		root := &entity.InfraTreeNode{}
		root.Create = now
		root.Update = now
		root.Type = infra.TreeMaas
		//root.Type = infra.TreeZone
		root.State = "alive"
		root.Info = v.DetailRepoValue
		root.Name = v.DetailRepoValue.ClusterName
		root.Guid = v.GUID

		for _, n := range v.RunTimeRepoValue.ClusterNodes {
			child := &entity.InfraTreeNode{}
			child.Create = now
			child.Update = now
			child.Type = infra.TreeMachine
			child.State = "alive"
			child.Info = n
			child.Name = n.NodeName
			child.Guid = v.GUID

			root.ChildData = append(root.ChildData, child)
		}
		root.ChildSize = len(v.RunTimeRepoValue.ClusterNodes)
		(*area)[v.AreaID].ChildData = append((*area)[v.AreaID].ChildData, root)
		(*areaG)[v.GUID] = root
	}
	return nil
}

func makeArea(areas []entity.Area, tree *entity.InfraTreeNode, area *map[string]*entity.InfraTreeNode) error {
	now := time.Now()
	tree.Create = now
	tree.Update = now
	tree.Type = infra.TreeZone
	tree.State = "alive"
	tree.Info = nil
	tree.Name = "glue"
	tree.Guid = ""

	for _, v := range areas {
		if err := makeAreaD(1, []string{fmt.Sprintf("%v", v.AreaID), v.Country, v.Province, v.City, v.County}, tree, now, area); err != nil {
			return err
		}
	}

	return nil
}

func makeAreaD(step int, area []string, parent *entity.InfraTreeNode, now time.Time, areaM *map[string]*entity.InfraTreeNode) error {
	if step == len(area) {
		return nil
	}

	var exist = false
	var child *entity.InfraTreeNode
	for _, v := range parent.ChildData {
		if v.Name == area[step] {
			exist = true
			child = v
		}
	}
	if !exist {
		child = &entity.InfraTreeNode{}
		child.Create = now
		child.Update = now
		child.Type = infra.TreeZone
		child.State = "alive"
		if step > 1 {
			areaInfo := map[string]interface{}{
				"area": area[1:step],
			}
			if step == len(area)-1 {
				areaInfo["area_id"] = area[0]
			}
			child.Info = areaInfo
		}
		child.Name = area[step]
		child.Guid = ""
		parent.ChildData = append(parent.ChildData, child)
		if step == len(area)-1 {
			(*areaM)[area[0]] = child
		}
	}

	parent.ChildSize = len(parent.ChildData)
	return makeAreaD(step+1, area, child, now, areaM)
}

//func (r *InfraTreeFactory) BuildTree() error {
//	var keys []string
//	if err := r.MetaRepo.FindKeysWithPrefix(infra.RootKey, &keys); err != nil {
//		return err
//	}
//
//	var tree = entity.InfraTreeNode{}
//	var now = time.Now()
//	tree.Create = now
//	tree.Update = now
//	tree.Name = "glue"
//	tree.Type = "zone"
//	tree.State = "alive"
//	tree.Info = nil
//
//	for _, v := range keys {
//		//key := filepath.ToSlash(filepath.Dir(v))
//		path, err := filepath.Rel(infra.RootKey, v)
//		if err != nil {
//			return fmt.Errorf("rel %v %v %v", infra.RootKey, v, err)
//		}
//		r.walkAgent(v, filepath.ToSlash(path), &tree)
//	}
//
//	var objs []entity.Instance
//	if err := r.MetaRepo.FindAll(&objs); err != nil {
//		return err
//	}
//
//	for _, obj := range objs {
//		for containName, containerInfo := range obj.RunTimeRepoValue.Containers {
//			leaf, err := walkToLeafAgent(obj.Platform, obj.GUID, containerInfo.NodeName, containName, &tree)
//			if err != nil {
//				fmt.Printf("WARN obj %v name %v info %v \n", obj.GUID, containName, containerInfo)
//				continue
//			}
//			makeApplicationTree(leaf, &obj, containName, containerInfo)
//			leaf.ChildSize = len(leaf.ChildData)
//		}
//	}
//
//	tree.ChildSize = len(tree.ChildData)
//	if err := r.MetaRepo.PutKV(infra.RootValueKey, tree); err != nil {
//		return err
//	}
//	return nil
//}

//func makeApplicationTree(node *entity.InfraTreeNode, obj *entity.Instance, containName string, containInfo interface{}) {
//	var newNode *entity.InfraTreeNode
//	for _, child := range node.ChildData {
//		if child.Guid == obj.GUID {
//			newNode = child
//		}
//	}
//	if newNode == nil {
//		newNode = &entity.InfraTreeNode{}
//		newNode.Name = obj.Name
//		newNode.Guid = obj.GUID
//		newNode.Type = "instance"
//		newNode.State = "alive"
//		newNode.Info = obj.DetailRepoValue
//		newNode.Create = time.Now()
//		newNode.Update = time.Now()
//		node.ChildData = append(node.ChildData, newNode)
//	}
//
//	var childNode = &entity.InfraTreeNode{}
//	childNode.Name = containName
//	childNode.Guid = obj.GUID
//	childNode.Type = "container"
//	childNode.State = "alive"
//	childNode.Info = containInfo
//	childNode.Create = time.Now()
//	childNode.Update = time.Now()
//
//	newNode.ChildData = append(newNode.ChildData, childNode)
//}

//func walkToLeafAgent(platform, guid, machine string, contain string, tree *entity.InfraTreeNode) (*entity.InfraTreeNode, error) {
//	switch platform {
//	case infra.PlatformKubernetes:
//		if tree.Guid == guid && tree.Name == machine && tree.Type == "machine" {
//			return tree, nil
//		}
//	default:
//		return nil, infra.UnKnownKind
//	}
//
//	if tree.Type == "machine" {
//		return nil, fmt.Errorf("not this node")
//	}
//
//	for _, obj := range tree.ChildData {
//		nextNode, err := walkToLeafAgent(platform, guid, machine, contain, obj)
//		if err == nil {
//			return nextNode, nil
//		}
//	}
//
//	return nil, fmt.Errorf("unknow error node %v", tree)
//}

//func getInstanceRuntimeAgents(data interface{}) (map[string][4]interface{}, error) {
//	//fmt.Printf("data: %v",data)
//	d, ok := data.(map[string]interface{})
//	if !ok {
//		return nil, fmt.Errorf("data is not map[string]interface")
//	}
//	agent, ok := d["agents"]
//	if !ok {
//		return nil, fmt.Errorf("data is not contain agent")
//	}
//	platform, ok := d["platform"]
//	if !ok {
//		return nil, fmt.Errorf("data is not contain platform")
//	}
//
//	cMap, ok := d["containers"]
//	if !ok {
//		return nil, fmt.Errorf("data is not contain platform")
//	}
//
//	var res = make(map[string][4]interface{})
//	switch platform.(string) {
//	case infra.PlatformKubernetes:
//		containInfo, ok := cMap.(map[string]interface{})
//		if !ok {
//			return nil, fmt.Errorf("info is not map")
//		}
//
//		for contain, containMapi := range containInfo {
//			containMap, ok := containMapi.(map[string]interface{})
//			if !ok {
//				return nil, fmt.Errorf("containers is not map")
//			}
//			nodeName, ok := containMap["node_name"]
//			if !ok {
//				return nil, fmt.Errorf("containers not container node name")
//			}
//			res[contain] = [4]interface{}{platform, agent, nodeName, containInfo[contain]}
//		}
//		//fmt.Printf("\nres %v\n",res)
//	}
//
//	return res, nil
//}

//func (r *InfraTreeFactory) walkAgent(gobal string, path string, obj *entity.InfraTreeNode) error {
//	paths := strings.Split(path, "/")
//	if len(paths) == 0 {
//		return nil
//	}
//
//	if len(paths) == 1 {
//		if paths[0] == "detail" {
//			var detail po.AgentDetail
//			if err := r.MetaRepo.FindKV(gobal, &detail); err != nil {
//				return fmt.Errorf("get detail info %v", err)
//			}
//
//			if detail.Platform != infra.PlatformKubernetes {
//				return infra.UnKnownKind
//			}
//
//			obj.Name = detail.ClusterName
//			obj.Type = "zone"
//
//			for _, nodeName := range detail.Nodes {
//				current := &entity.InfraTreeNode{}
//				var now = time.Now()
//				current.Create = now
//				current.Update = now
//				current.Name = nodeName
//				current.Type = "machine"
//				current.State = "alive"
//				current.Info = nil
//				current.Guid = obj.Guid
//				obj.ChildData = append(obj.ChildData, current)
//			}
//
//		}
//		return nil
//	}
//
//	var current *entity.InfraTreeNode
//	if len(paths) == 2 && paths[1] == "detail" {
//		var (
//			detail po.AgentDetail
//			newObj entity.Agent
//		)
//		if err := r.MetaRepo.FindKV(gobal, &detail); err != nil {
//			return fmt.Errorf("get detail %v %v", gobal, err)
//		}
//
//		if err := r.MetaRepo.FindOne(&newObj, map[string]interface{}{
//			"guid": paths[0],
//		}); err != nil {
//			return fmt.Errorf("get agent %v %v", gobal, err)
//		}
//
//		current = &entity.InfraTreeNode{}
//		current.Name = newObj.HostName
//		current.Guid = newObj.GUID
//		current.Type = "machine"
//		current.State = "alive"
//		current.Info = detail
//		current.Create = time.Now()
//		current.Update = time.Now()
//		obj.ChildData = append(obj.ChildData, current)
//	}
//
//	if len(paths) > 2 {
//		var match = false
//		for _, child := range obj.ChildData {
//			if child.Name == paths[0] {
//				match = true
//				current = child
//			}
//		}
//		if !match {
//			current = &entity.InfraTreeNode{}
//			current.Name = paths[0]
//			current.Guid = ""
//			current.Type = "zone"
//			current.State = "alive"
//			current.Info = nil
//			current.Create = time.Now()
//			current.Update = time.Now()
//			obj.ChildData = append(obj.ChildData, current)
//		}
//	}
//
//	nextPath, err := filepath.Rel(paths[0], path)
//	if err != nil {
//		return fmt.Errorf("rel %v %v %v", paths[0], gobal, err)
//	}
//
//	if err = r.walkAgent(gobal, filepath.ToSlash(nextPath), current); err != nil {
//		return err
//	}
//	if current != nil {
//		current.ChildSize = len(current.ChildData)
//	}
//	return nil
//}
